%{
/*
 * TODO language 
 * Copyright (c) 2010 TODO Team
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
 
#include <cstdlib>
#include <fstream>
#include <errno.h>
#include <limits.h>
#include <string>
#include "API.h"
#include "driver.h"
#include "parser.hpp"
#include "types.h"

#undef yywrap
#define yywrap() 1

#define yyterminate() return token::END
#define YY_USER_ACTION  yylloc->columns(yyleng);

%}

%option noyywrap nounput batch debug

ident    [a-zA-Z_][a-zA-Z0-9_]*
double   ([0-9]+[\.][0-9]+)
integer  (0|[1-9][0-9]*)
hexint   0x[0-9a-zA-Z]+
octint   0[0-7]+
blank    [ \t]
string   (["]([^\\"]*|"\\"["]?)*["]|[']([^\\']*|"\\"[']?)*['])
anychar  (.|[\n])
tokens   [.+*%^|/:;=,\(\)\[\]{}<>-]

%x ST_COMMENT
%x ST_MULTILINE_COMMENT

%%

%{
yylloc->step();
typedef yy::todo_parser::token token;
%}

{blank}+   yylloc->step();
[\n]+      yylloc->lines(yyleng); yylloc->step();

"<<" { return token::SHIFT_LEFT; }

"<<=" { return token::SHIFT_LEFT_EQUAL; }

">>" { return token::SHIFT_RIGHT; }

">>=" { return token::SHIFT_RIGHT_EQUAL; }

">=" { return token::GREATER_EQUAL; }

"|=" { return token::BW_OR_EQUAL; }

"|" { return token::BW_OR; }

"&=" { return token::BW_AND_EQUAL; }

"&" { return token::BW_AND; }

"^" { return token::BW_XOR; }

"^=" { return token::XOR_EQUAL; }

"<=" { return token::LESS_EQUAL; }

"<" { return token::LESS; }

">" { return token::GREATER; }

"++" { return token::INCREMENT; }

"--" { return token::DECREMENT; }

"==" { return token::EQUAL; }

"!=" { return token::NOT_EQUAL; }

"=" { return token::ASSIGN; }

"-=" { return token::MINUS_EQUAL; }

"+=" { return token::PLUS_EQUAL; }

"*=" { return token::MULT_EQUAL; }

"/=" { return token::DIV_EQUAL; }

"%=" { return token::MOD_EQUAL; }

"::" { return token::MOD_SEPARATOR; }

"exit" { return token::EXIT; }

"import" { return token::IMPORT; }

"//" {
	BEGIN(ST_COMMENT);
}

<ST_COMMENT>[^\n]* {
	BEGIN(INITIAL);
}

"/*" {
	BEGIN(ST_MULTILINE_COMMENT);
}

<ST_MULTILINE_COMMENT>[^*]+         ;

<ST_MULTILINE_COMMENT>"*/" {
	BEGIN(INITIAL);
}

<ST_MULTILINE_COMMENT>"*"           ;

"module"    { return token::MODULE; }
"return"    { return token::RETURN; }
"if"        { return token::IF; }
"else"      { return token::ELSE; }
"elsif"     { return token::ELSIF; }
"println"   { return token::PRINTLN; }
"print"     { return token::PRINT; }
"break"     { return token::BREAK; }
"loop"      { return token::LOOP; }
"var"       { return token::VAR; }
"map"       { return token::MAP; }
"vector"    { return token::VECTOR; }
"int"       { yylval->type = TYPE_P("int");    yylval->data = NULL; return token::INTEGER; }
"double"    { yylval->type = TYPE_P("double"); yylval->data = NULL; return token::DOUBLE; }
"string"    { yylval->type = TYPE_P("string"); yylval->data = NULL; return token::STRING; }
"bool"      { yylval->type = TYPE_P("bool");   yylval->data = NULL; return token::BOOL; }
"object"    { yylval->type = TYPE_P("object"); yylval->data = NULL; return token::OBJECT; }
"def"       { return token::DEF; }
"case"      { return token::CASE; }
"when"      { return token::WHEN; }
"others"    { return token::OTHERS; }
"true"      {
	yylval->data_type = todo::TODO_CONSTANT;
	yylval->data = NEW("bool");
	BOOL_V(yylval->data) = true;
	return token::TRUE;
}
"false"     {
	yylval->data_type = todo::TODO_CONSTANT;
	yylval->data = NEW("bool");
	BOOL_V(yylval->data) = false;
	return token::FALSE; 
}

{string} {
	std::string strtext(yytext+1, std::string(yytext).size()-2);	
	size_t found = 0;
	
	// Handle sequence char
	while (1) {
		found = strtext.find('\\', found);
		if (found == std::string::npos) {
			break;
		}
		if (yytext[0] == '"') {
			switch (strtext[found+1]) {
				case 'n': strtext.replace(int(found), 2, 1, '\n'); break;
				case 'r': strtext.replace(int(found), 2, 1, '\r'); break;
				case 't': strtext.replace(int(found), 2, 1, '\t'); break;
				case 'v': strtext.replace(int(found), 2, 1, '\v'); break;
				case '"': strtext.replace(int(found), 2, 1, '"'); break;
				case '\\': strtext.replace(int(found), 2, 1, '\\'); ++found; break;
				default: ++found; break;
			}
		} else {
			switch (strtext[found+1]) {
				case '\'': strtext.replace(int(found), 2, 1, '\''); break;
				default: ++found; break;
			}
		}
	}
	yylval->data_type = todo::TODO_CONSTANT;
	yylval->data = NEW("string");
	STR_P(yylval->data)->assign(strtext);

	return token::STR;
}

{integer} {
	errno = 0;
	long n = strtol(yytext, NULL, 10);
	
	if (!(INT_MIN <= n && n <= INT_MAX && errno != ERANGE)) {
		driver.error(*yylloc, "integer is out of range");
	}
	
	yylval->data_type = todo::TODO_CONSTANT;
	yylval->data = NEW("int");
	INT_V(yylval->data) = n;

	return token::NUM_INTEGER;
}

{hexint} {
	errno = 0;
	long n = 0;
	sscanf(yytext+2, "%x", &n);

	if (!(INT_MIN <= n && n <= INT_MAX && errno != ERANGE)) {
		driver.error(*yylloc, "integer is out of range");
	}
	
	yylval->data_type = todo::TODO_CONSTANT;
	yylval->data = NEW("int");
	INT_V(yylval->data) = n;

	return token::NUM_INTEGER;
}

{octint} {
	errno = 0;
	long n = 0;
	sscanf(yytext+1, "%o", &n);

	if (!(INT_MIN <= n && n <= INT_MAX && errno != ERANGE)) {
		driver.error(*yylloc, "integer is out of range");
	}
	
	yylval->data_type = todo::TODO_CONSTANT;
	yylval->data = NEW("int");
	INT_V(yylval->data) = n;

	return token::NUM_INTEGER;
}

{double} {
	yylval->data_type = todo::TODO_CONSTANT;
	yylval->data = NEW("double");
	DOUBLE_V(yylval->data) = strtod(yytext, NULL);
	
	return token::NUM_DOUBLE;
}

{ident} {
	yylval->data_type = todo::TODO_VAR;
	yylval->data = NEW("string");
	STR_P(yylval->data)->assign(yytext);
	
	return token::IDENT;
}

{tokens} {
	return yy::todo_parser::token_type(yytext[0]);
}

.  {
	driver.error(*yylloc, "invalid character '" + std::string(1, yytext[0]) + "'");
}

%%

void todo_driver::scan_begin()
{
	yy_flex_debug = trace_scanning;

	if (file == "-") {
		yyin = stdin;
	} else if (input.size()) {
		yy_scan_string(input.c_str());
	} else if (!(yyin = fopen(file.c_str(), "r"))) {
		error("cannot open " + file);
		exit(1);
	} else {
		std::ifstream inFile;
		std::string source, str;
		inFile.open(file.c_str());
		
		while (inFile >> str) {
			source += str + " ";
		}
		yy_scan_string(source.c_str());
	}
	
	scanning = true;
}

void todo_driver::scan_end()
{
	// yy_delete_buffer(YY_CURRENT_BUFFER);
	
	if (yyin) {
		fclose(yyin);
	}	
}
